package com.kunminx.puremusic.utils;

import static android.Manifest.permission.ACCESS_NETWORK_STATE;
import static android.Manifest.permission.ACCESS_WIFI_STATE;
import static android.Manifest.permission.CHANGE_WIFI_STATE;
import static android.Manifest.permission.INTERNET;
import static android.Manifest.permission.MODIFY_PHONE_STATE;
import static android.content.Context.WIFI_SERVICE;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.telephony.TelephonyManager;
import android.text.format.Formatter;
import android.util.Log;

import androidx.annotation.RequiresPermission;

import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;

/**
 * <pre>
 *     author: Blankj
 *     blog  : http://blankj.com
 *     time  : 2016/08/02
 *     desc  : utils about network
 * </pre>
 */
public final class NetworkUtils {

  private NetworkUtils() {
    throw new UnsupportedOperationException("u can't instantiate me...");
  }

  /**
   * Open the settings of wireless.
   */
  public static void openWirelessSettings() {
    Utils.getApp().startActivity(
            new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS)
                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    );
  }

  /**
   * Return whether network is connected.
   * <p>Must hold {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />}</p>
   *
   * @return {@code true}: connected<br>{@code false}: disconnected
   */
  @RequiresPermission(ACCESS_NETWORK_STATE)
  public static boolean isConnected() {
    NetworkInfo info = getActiveNetworkInfo();
    return info != null && info.isConnected();
  }

  /**
   * Return whether network is available using ping.
   * <p>Must hold {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
   * <p>The default ping ip: 223.5.5.5</p>
   *
   * @return {@code true}: yes<br>{@code false}: no
   */
  @RequiresPermission(INTERNET)
  public static boolean isAvailableByPing() {
    return isAvailableByPing(null);
  }

  /**
   * Return whether network is available using ping.
   * <p>Must hold {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
   *
   * @param ip The ip address.
   * @return {@code true}: yes<br>{@code false}: no
   */
  @RequiresPermission(INTERNET)
  public static boolean isAvailableByPing(String ip) {
    if (ip == null || ip.length() <= 0) {
      // default ping ip
      ip = "223.5.5.5";
    }
    ShellUtils.CommandResult result = ShellUtils.execCmd(String.format("ping -c 1 %s", ip), false);
    boolean ret = result.result == 0;
    if (result.errorMsg != null) {
      Log.d("NetworkUtils", "isAvailableByPing() called" + result.errorMsg);
    }
    if (result.successMsg != null) {
      Log.d("NetworkUtils", "isAvailableByPing() called" + result.successMsg);
    }
    return ret;
  }

  @RequiresPermission(INTERNET)
  public static void isAvailableByDns(String ip) {

  }

  /**
   * Return whether mobile data is enabled.
   *
   * @return {@code true}: enabled<br>{@code false}: disabled
   */
  public static boolean getMobileDataEnabled() {
    try {
      TelephonyManager tm =
              (TelephonyManager) Utils.getApp().getSystemService(Context.TELEPHONY_SERVICE);
      if (tm == null) {
        return false;
      }
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        return tm.isDataEnabled();
      }
      @SuppressLint("PrivateApi")
      Method getMobileDataEnabledMethod =
              tm.getClass().getDeclaredMethod("getDataEnabled");
      if (null != getMobileDataEnabledMethod) {
        return (boolean) getMobileDataEnabledMethod.invoke(tm);
      }
    } catch (Exception e) {
      Log.e("NetworkUtils", "getMobileDataEnabled: ", e);
    }
    return false;
  }

  /**
   * Enable or disable mobile data.
   * <p>Must hold {@code android:sharedUserId="android.uid.system"},
   * {@code <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />}</p>
   *
   * @param enabled True to enabled, false otherwise.
   * @return {@code true}: success<br>{@code false}: fail
   */
  @RequiresPermission(MODIFY_PHONE_STATE)
  public static boolean setMobileDataEnabled(final boolean enabled) {
    try {
      TelephonyManager tm =
              (TelephonyManager) Utils.getApp().getSystemService(Context.TELEPHONY_SERVICE);
      if (tm == null) {
        return false;
      }
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        tm.setDataEnabled(enabled);
        return false;
      }
      Method setDataEnabledMethod =
              tm.getClass().getDeclaredMethod("setDataEnabled", boolean.class);
      if (null != setDataEnabledMethod) {
        setDataEnabledMethod.invoke(tm, enabled);
        return true;
      }
    } catch (Exception e) {
      Log.e("NetworkUtils", "setMobileDataEnabled: ", e);
    }
    return false;
  }

  /**
   * Return whether using mobile data.
   * <p>Must hold {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />}</p>
   *
   * @return {@code true}: yes<br>{@code false}: no
   */
  @RequiresPermission(ACCESS_NETWORK_STATE)
  public static boolean isMobileData() {
    NetworkInfo info = getActiveNetworkInfo();
    return null != info
            && info.isAvailable()
            && info.getType() == ConnectivityManager.TYPE_MOBILE;
  }

  /**
   * Return whether using 4G.
   * <p>Must hold {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />}</p>
   *
   * @return {@code true}: yes<br>{@code false}: no
   */
  @RequiresPermission(ACCESS_NETWORK_STATE)
  public static boolean is4G() {
    NetworkInfo info = getActiveNetworkInfo();
    return info != null
            && info.isAvailable()
            && info.getSubtype() == TelephonyManager.NETWORK_TYPE_LTE;
  }

  /**
   * Return whether wifi is enabled.
   * <p>Must hold {@code <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />}</p>
   *
   * @return {@code true}: enabled<br>{@code false}: disabled
   */
  @RequiresPermission(ACCESS_WIFI_STATE)
  public static boolean getWifiEnabled() {
    @SuppressLint("WifiManagerLeak")
    WifiManager manager = (WifiManager) Utils.getApp().getSystemService(WIFI_SERVICE);
    if (manager == null) {
      return false;
    }
    return manager.isWifiEnabled();
  }

  /**
   * Enable or disable wifi.
   * <p>Must hold {@code <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />}</p>
   *
   * @param enabled True to enabled, false otherwise.
   */
  @RequiresPermission(CHANGE_WIFI_STATE)
  public static void setWifiEnabled(final boolean enabled) {
    @SuppressLint("WifiManagerLeak")
    WifiManager manager = (WifiManager) Utils.getApp().getSystemService(WIFI_SERVICE);
    if (manager == null) {
      return;
    }
    if (enabled == manager.isWifiEnabled()) {
      return;
    }
    manager.setWifiEnabled(enabled);
  }

  /**
   * Return whether wifi is connected.
   * <p>Must hold {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />}</p>
   *
   * @return {@code true}: connected<br>{@code false}: disconnected
   */
  @RequiresPermission(ACCESS_NETWORK_STATE)
  public static boolean isWifiConnected() {
    ConnectivityManager cm =
            (ConnectivityManager) Utils.getApp().getSystemService(Context.CONNECTIVITY_SERVICE);
    if (cm == null) {
      return false;
    }
    NetworkInfo ni = cm.getActiveNetworkInfo();
    return ni != null && ni.getType() == ConnectivityManager.TYPE_WIFI;
  }

  /**
   * Return whether wifi is available.
   * <p>Must hold {@code <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />},
   * {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
   *
   * @return {@code true}: available<br>{@code false}: unavailable
   */
  @RequiresPermission(allOf = {ACCESS_WIFI_STATE, INTERNET})
  public static boolean isWifiAvailable() {
    return getWifiEnabled() && isAvailableByPing();
  }

  /**
   * Return the name of network operate.
   *
   * @return the name of network operate
   */
  public static String getNetworkOperatorName() {
    TelephonyManager tm =
            (TelephonyManager) Utils.getApp().getSystemService(Context.TELEPHONY_SERVICE);
    if (tm == null) {
      return "";
    }
    return tm.getNetworkOperatorName();
  }

  /**
   * Return type of network.
   * <p>Must hold {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />}</p>
   *
   * @return type of network
   * <ul>
   * <li>{@link NetworkUtils.NetworkType#NETWORK_ETHERNET} </li>
   * <li>{@link NetworkUtils.NetworkType#NETWORK_WIFI    } </li>
   * <li>{@link NetworkUtils.NetworkType#NETWORK_4G      } </li>
   * <li>{@link NetworkUtils.NetworkType#NETWORK_3G      } </li>
   * <li>{@link NetworkUtils.NetworkType#NETWORK_2G      } </li>
   * <li>{@link NetworkUtils.NetworkType#NETWORK_UNKNOWN } </li>
   * <li>{@link NetworkUtils.NetworkType#NETWORK_NO      } </li>
   * </ul>
   */
  @RequiresPermission(ACCESS_NETWORK_STATE)
  public static NetworkType getNetworkType() {
    if (isEthernet()) {
      return NetworkType.NETWORK_ETHERNET;
    }
    NetworkInfo info = getActiveNetworkInfo();
    if (info != null && info.isAvailable()) {
      if (info.getType() == ConnectivityManager.TYPE_WIFI) {
        return NetworkType.NETWORK_WIFI;
      } else if (info.getType() == ConnectivityManager.TYPE_MOBILE) {
        switch (info.getSubtype()) {
          case TelephonyManager.NETWORK_TYPE_GSM:
          case TelephonyManager.NETWORK_TYPE_GPRS:
          case TelephonyManager.NETWORK_TYPE_CDMA:
          case TelephonyManager.NETWORK_TYPE_EDGE:
          case TelephonyManager.NETWORK_TYPE_1xRTT:
          case TelephonyManager.NETWORK_TYPE_IDEN:
            return NetworkType.NETWORK_2G;

          case TelephonyManager.NETWORK_TYPE_TD_SCDMA:
          case TelephonyManager.NETWORK_TYPE_EVDO_A:
          case TelephonyManager.NETWORK_TYPE_UMTS:
          case TelephonyManager.NETWORK_TYPE_EVDO_0:
          case TelephonyManager.NETWORK_TYPE_HSDPA:
          case TelephonyManager.NETWORK_TYPE_HSUPA:
          case TelephonyManager.NETWORK_TYPE_HSPA:
          case TelephonyManager.NETWORK_TYPE_EVDO_B:
          case TelephonyManager.NETWORK_TYPE_EHRPD:
          case TelephonyManager.NETWORK_TYPE_HSPAP:
            return NetworkType.NETWORK_3G;

          case TelephonyManager.NETWORK_TYPE_IWLAN:
          case TelephonyManager.NETWORK_TYPE_LTE:
            return NetworkType.NETWORK_4G;

          default:
            String subtypeName = info.getSubtypeName();
            if ("TD-SCDMA".equalsIgnoreCase(subtypeName)
                    || "WCDMA".equalsIgnoreCase(subtypeName)
                    || "CDMA2000".equalsIgnoreCase(subtypeName)) {
              return NetworkType.NETWORK_3G;
            }
        }
      }
    }
    return NetworkType.NETWORK_UNKNOWN;
  }

  /**
   * Return whether using ethernet.
   * <p>Must hold
   * {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />}</p>
   *
   * @return {@code true}: yes<br>{@code false}: no
   */
  @RequiresPermission(ACCESS_NETWORK_STATE)
  private static boolean isEthernet() {
    final ConnectivityManager cm =
            (ConnectivityManager) Utils.getApp().getSystemService(Context.CONNECTIVITY_SERVICE);
    if (cm == null) {
      return false;
    }
    final NetworkInfo info = cm.getNetworkInfo(ConnectivityManager.TYPE_ETHERNET);
    if (info == null) {
      return false;
    }
    NetworkInfo.State state = info.getState();
    if (null == state) {
      return false;
    }
    return state == NetworkInfo.State.CONNECTED || state == NetworkInfo.State.CONNECTING;
  }

  @RequiresPermission(ACCESS_NETWORK_STATE)
  private static NetworkInfo getActiveNetworkInfo() {
    ConnectivityManager cm =
            (ConnectivityManager) Utils.getApp().getSystemService(Context.CONNECTIVITY_SERVICE);
    if (cm == null) {
      return null;
    }
    return cm.getActiveNetworkInfo();
  }

  /**
   * Return the ip address.
   * <p>Must hold {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
   *
   * @param useIPv4 True to use ipv4, false otherwise.
   * @return the ip address
   */
  @RequiresPermission(INTERNET)
  public static String getIPAddress(final boolean useIPv4) {
    try {
      Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
      LinkedList<InetAddress> adds = new LinkedList<>();
      while (nis.hasMoreElements()) {
        NetworkInterface ni = nis.nextElement();
        // To prevent phone of xiaomi return "10.0.2.15"
        if (!ni.isUp() || ni.isLoopback()) {
          continue;
        }
        Enumeration<InetAddress> addresses = ni.getInetAddresses();
        while (addresses.hasMoreElements()) {
          adds.addFirst(addresses.nextElement());
        }
      }
      for (InetAddress add : adds) {
        if (!add.isLoopbackAddress()) {
          String hostAddress = add.getHostAddress();
          boolean isIPv4 = hostAddress.indexOf(':') < 0;
          if (useIPv4) {
            if (isIPv4) {
              return hostAddress;
            }
          } else {
            if (!isIPv4) {
              int index = hostAddress.indexOf('%');
              return index < 0
                      ? hostAddress.toUpperCase()
                      : hostAddress.substring(0, index).toUpperCase();
            }
          }
        }
      }
    } catch (SocketException e) {
      e.printStackTrace();
    }
    return "";
  }

  /**
   * Return the ip address of broadcast.
   *
   * @return the ip address of broadcast
   */
  public static String getBroadcastIpAddress() {
    try {
      Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
      LinkedList<InetAddress> adds = new LinkedList<>();
      while (nis.hasMoreElements()) {
        NetworkInterface ni = nis.nextElement();
        if (!ni.isUp() || ni.isLoopback()) {
          continue;
        }
        List<InterfaceAddress> ias = ni.getInterfaceAddresses();
        for (int i = 0, size = ias.size(); i < size; i++) {
          InterfaceAddress ia = ias.get(i);
          InetAddress broadcast = ia.getBroadcast();
          if (broadcast != null) {
            return broadcast.getHostAddress();
          }
        }
      }
    } catch (SocketException e) {
      e.printStackTrace();
    }
    return "";
  }

  /**
   * Return the domain address.
   * <p>Must hold {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
   *
   * @param domain The name of domain.
   * @return the domain address
   */
  @RequiresPermission(INTERNET)
  public static String getDomainAddress(final String domain) {
    InetAddress inetAddress;
    try {
      inetAddress = InetAddress.getByName(domain);
      return inetAddress.getHostAddress();
    } catch (UnknownHostException e) {
      e.printStackTrace();
      return "";
    }
  }

  /**
   * Return the ip address by wifi.
   *
   * @return the ip address by wifi
   */
  @RequiresPermission(ACCESS_WIFI_STATE)
  public static String getIpAddressByWifi() {
    @SuppressLint("WifiManagerLeak")
    WifiManager wm = (WifiManager) Utils.getApp().getSystemService(Context.WIFI_SERVICE);
    if (wm == null) {
      return "";
    }
    return Formatter.formatIpAddress(wm.getDhcpInfo().ipAddress);
  }

  /**
   * Return the gate way by wifi.
   *
   * @return the gate way by wifi
   */
  @RequiresPermission(ACCESS_WIFI_STATE)
  public static String getGatewayByWifi() {
    @SuppressLint("WifiManagerLeak")
    WifiManager wm = (WifiManager) Utils.getApp().getSystemService(Context.WIFI_SERVICE);
    if (wm == null) {
      return "";
    }
    return Formatter.formatIpAddress(wm.getDhcpInfo().gateway);
  }

  /**
   * Return the net mask by wifi.
   *
   * @return the net mask by wifi
   */
  @RequiresPermission(ACCESS_WIFI_STATE)
  public static String getNetMaskByWifi() {
    @SuppressLint("WifiManagerLeak")
    WifiManager wm = (WifiManager) Utils.getApp().getSystemService(Context.WIFI_SERVICE);
    if (wm == null) {
      return "";
    }
    return Formatter.formatIpAddress(wm.getDhcpInfo().netmask);
  }

  /**
   * Return the server address by wifi.
   *
   * @return the server address by wifi
   */
  @RequiresPermission(ACCESS_WIFI_STATE)
  public static String getServerAddressByWifi() {
    @SuppressLint("WifiManagerLeak")
    WifiManager wm = (WifiManager) Utils.getApp().getSystemService(Context.WIFI_SERVICE);
    if (wm == null) {
      return "";
    }
    return Formatter.formatIpAddress(wm.getDhcpInfo().serverAddress);
  }

  public enum NetworkType {
    NETWORK_ETHERNET,
    NETWORK_WIFI,
    NETWORK_4G,
    NETWORK_3G,
    NETWORK_2G,
    NETWORK_UNKNOWN,
    NETWORK_NO
  }

  public interface Callback {
    void call(boolean isSuccess);
  }
}
